//
//  Csv.cpp
//  JewelsJunior
//
//  Created by Lixiaolong on 2021/5/7.
//

#include <algorithm>
#include "Csv.hpp"

Csv::Csv() {}
Csv::Csv(const char& separated) {
    this->separated = separated;
}
Csv::Csv(const std::string& csv, const char& separated/*=','*/) {
	new (this)Csv(csv, true, separated);
}
Csv::Csv(const std::string& csv, const bool& head, const char& separated/*=','*/) {
	std::string s = csv;
    trim(s);
	size_t p = s.find("\r\n");
	while (p != std::string::npos) {
		s.replace(p, 2, "\n");
		p = s.find("\r\n", p+1);
	}
	State state = State::kUnquoted;
	for (const char& ch : s) {
		switch (state) {
			case State::kUnquoted: {
				if (ch == separated) {
					body.back().push_back("");
				} else if (ch == '"') {
					state = State::kQuoted;
					if (!csv.empty()) {
						if (!body.back().empty() && !body.back().back().empty()) {
							state = State::kQuoted3;
							body.back().back().push_back(ch);
						}
					}
				} else if (ch == '\n') {
					while (body.back().size() < body.front().size()) {
						body.back().push_back("");
					}
					body.push_back({});
				} else {
					if (body.empty()) {
						body.push_back({});
					}
					if (body.back().empty()) {
						body.back().push_back("");
					}
					body.back().back().push_back(ch);
				}
				break;
			}
			case State::kQuoted: {
				if (ch == '"') {
					state = State::kQuoted2;
				} else {
					body.back().back().push_back(ch);
				}
				break;
			}
			case State::kQuoted2: {
				if (ch == separated) {
					state = State::kUnquoted;
					body.back().push_back("");
				} else if (ch == '"') {
					state = State::kQuoted;
					body.back().back().push_back(ch);
				} else {
					state = State::kUnquoted;
					if (ch == '\n') {
						body.push_back({});
					}
				}
				break;
			}
			case State::kQuoted3: {
				if (ch == '"') {
					state = State::kUnquoted;
					body.back().back().push_back(ch);
				} else {
					body.back().back().push_back(ch);
				}
				break;
			}
		}
	}
    if (!body.empty()) {
        while (body.back().size() < body.front().size()) {
            body.back().push_back("");
        }
        if (head) {
            this->head = body.front();
            body.erase(body.begin());
        }
    }
	this->separated = separated;
}
Csv::Csv(const std::vector<std::vector<std::string>>& csv, const char& separated/*=','*/) {
	new (this)Csv(csv, true, separated);
}
Csv::Csv(const std::vector<std::vector<std::string>>& csv, const bool& head, const char& separated/*=','*/) {
	this->body = csv;
	if (head && !this->body.empty()) {
		this->head = this->body.front();
		this->body.erase(this->body.begin());
	}
	this->separated = separated;
}
Csv::Csv(const std::vector<std::vector<std::string>>& body, const std::vector<std::string>& head, const char& separated/*=','*/) {
	this->body = body;
	this->head = head;
	this->separated = separated;
}

Csv::Csv(const std::vector<std::string>& head, const char& separated/*=','*/) {
    this->head = head;
    this->separated = separated;
}

void Csv::erase(const std::string& key) {
	auto it = std::find(head.begin(), head.end(), key);
	if (it != head.end()) {
        erase_column(it - head.begin());
	}
}
void Csv::erase_row(const size_t& row) {
    if (row >= body.size()) {
        return;
    }
    body.erase(body.begin() + row);
}
void Csv::erase_column(const size_t& column) {
    if (column >= head.size()) {
        return;
    }
    for (auto& l : body) {
        l.erase(l.begin() + column);
    }
    head.erase(head.begin() + column);
}

std::map<std::string, std::string> Csv::map(const size_t& r) const {
	std::map<std::string, std::string> ret = {};
	if (r < body.size()) {
		auto& row = body[r];
		for (size_t i=0; i!=head.size(); ++i) {
			ret[head[i]] = row[i];
		}
	}
	return ret;
}

std::string Csv::to_string(const char& separated) const {
    std::string s = join(head, separated);
	for (auto& row : body) {
		if (!s.empty()) {
			s.append("\n");
		}
        s.append(join(row, separated));
	}
	return s;
}

std::string Csv::join(const std::vector<std::string>& items, const char& separated) {
	std::string s;
	for (size_t i=0; i!=items.size(); ++i) {
		if (i) {
			s.append(1, separated);
		}
		auto t = items[i];
		s.append(t);
	}
	return s;
}
void Csv::trim(std::string& text) {
    trim_left(text);
    trim_right(text);
}
void Csv::trim_left(std::string& text) {
    auto it = std::find_if(text.begin(), text.end(), [](int ch) {
        return !std::isspace(ch);
    });
    text.erase(text.begin(), it);

}
void Csv::trim_right(std::string& text) {
    auto it = std::find_if(text.rbegin(), text.rend(), [](int ch) {
        return !std::isspace(ch);
    }).base();
    text.erase(it, text.end());
}
